﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
using Microsoft.Kinect;
using Microsoft.Speech.AudioFormat;
using Microsoft.Speech.Recognition;

// PowerPoint連携用
using PowerPoint = Microsoft.Office.Interop.PowerPoint;

namespace KinectPowerPointControl_sample1_Fukidashi
{
    //スケルトンの１時点での断面を保持する構造体
    public struct SkeletonLog
    {
        public JointCollectionEquivalent joints; //取得されたスケルトン
        public DateTime time; //スケルトンを取得した時間

        public SkeletonLog(JointCollectionEquivalent joints, DateTime dateTime)
        {
            this.joints = joints;
            this.time = dateTime;
        }
    }

    //スケルトンのDeepCopy、JointCollectionのコンストラクトができないので、相当する構造体を自作

    public class JointCollectionEquivalent
    {
        private Dictionary<JointType, Joint> joints;

        public JointCollectionEquivalent(JointCollection jc)
        {
            this.joints = new Dictionary<JointType, Joint>();
            foreach (JointType jt in Enum.GetValues(typeof(JointType)))
            {
                this.joints.Add(jt, jc[jt]);
            }
        }

        public Joint this[JointType jt] { get { return this.joints[jt]; } }
    }

    public partial class MainWindow : Window
    {
        private const bool IS_COLOR_VISIBLE = true;

        KinectSensor sensor;

        byte[] colorBytes;
        Skeleton[] skeletons;

        SolidColorBrush activeBrush = new SolidColorBrush(Colors.Green);
        SolidColorBrush inactiveBrush = new SolidColorBrush(Colors.Red);

        //ジェスチャー検出用
        LinkedList<SkeletonLog> recentSkeletons; //直近のスケルトン履歴を保持（Firstが最新）
        private const int LOGGING_INTERVAL = 300 * 1000 * 10; //スケルトン履歴の取得間隔（100ナノ秒）
        private const int LOGGING_VERSIONS = 20; //スケルトン履歴の断面数

        // PowerPoint連携用
        PowerPointController ppController;

        public MainWindow()
        {
            InitializeComponent();

            //Runtime initialization is handled when the window is opened. When the window
            //is closed, the runtime MUST be unitialized.
            this.Loaded += new RoutedEventHandler(MainWindow_Loaded);
            //Handle the content obtained from the video camera, once received.
            
            // キー操作
            this.KeyDown += new KeyEventHandler(MainWindow_KeyDown);


            //ジェスチャー検出用
            this.recentSkeletons = new LinkedList<SkeletonLog>();

            // PowerPoint連携用
            this.ppController = new PowerPointController();
        }

        void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            sensor = KinectSensor.KinectSensors.FirstOrDefault();

            if (sensor == null)
            {
                MessageBox.Show("This application requires a Kinect sensor.");
                this.Close();
            }
            
            sensor.Start();

            if (IS_COLOR_VISIBLE)
            {
                sensor.ColorStream.Enable(ColorImageFormat.RgbResolution640x480Fps30);
                sensor.ColorFrameReady += new EventHandler<ColorImageFrameReadyEventArgs>(sensor_ColorFrameReady);
            }

            sensor.DepthStream.Enable(DepthImageFormat.Resolution320x240Fps30);
            sensor.SkeletonStream.Enable();
            sensor.SkeletonFrameReady += new EventHandler<SkeletonFrameReadyEventArgs>(sensor_SkeletonFrameReady);

            sensor.ElevationAngle = 0;

            Application.Current.Exit += new ExitEventHandler(Current_Exit);
        }

        void Current_Exit(object sender, ExitEventArgs e)
        {

            this.ppController.exit();

            if (sensor != null)
            {
                sensor.AudioSource.Stop();
                sensor.Stop();
                sensor.Dispose();
                sensor = null;
            }
        }

        void sensor_ColorFrameReady(object sender, ColorImageFrameReadyEventArgs e)
        {
            using (var image = e.OpenColorImageFrame())
            {
                if (image == null)
                    return;

                if (colorBytes == null ||
                    colorBytes.Length != image.PixelDataLength)
                {
                    colorBytes = new byte[image.PixelDataLength];
                }

                image.CopyPixelDataTo(colorBytes);

                //You could use PixelFormats.Bgr32 below to ignore the alpha,
                //or if you need to set the alpha you would loop through the bytes 
                //as in this loop below
                int length = colorBytes.Length;
                for (int i = 0; i < length; i += 4)
                {
                    colorBytes[i + 3] = 255;
                }

                BitmapSource source = BitmapSource.Create(image.Width,
                    image.Height,
                    96,
                    96,
                    PixelFormats.Bgra32,
                    null,
                    colorBytes,
                    image.Width * image.BytesPerPixel);
                videoImage.Source = source;
            }
        }


        void sensor_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
        {
            using (var skeletonFrame = e.OpenSkeletonFrame())
            {
                if (skeletonFrame == null)
                    return;

                if (skeletons == null ||
                    skeletons.Length != skeletonFrame.SkeletonArrayLength)
                {
                    skeletons = new Skeleton[skeletonFrame.SkeletonArrayLength];
                }

                skeletonFrame.CopySkeletonDataTo(skeletons);

                // もっともカメラに近い人物のスケルトンを取得
                Skeleton closestSkeleton = (from s in skeletons
                                            where s.TrackingState == SkeletonTrackingState.Tracked &&
                                                  s.Joints[JointType.Head].TrackingState == JointTrackingState.Tracked
                                            select s).OrderBy(s => s.Joints[JointType.Head].Position.Z)
                                                    .FirstOrDefault();

                if (closestSkeleton == null)
                    return;

                // スケルトンを描画する
                if (skeletonFrame != null)
                {
                    DrawSkeleton(sender as KinectSensor, skeletonFrame);
                }

                JointCollectionEquivalent copyOfJointsOfClosestSkeleton = new JointCollectionEquivalent(closestSkeleton.Joints);

                //直近のジェスチャー履歴を作成する
                if (recentSkeletons.Count == 0 || DateTime.Now.Ticks - recentSkeletons.Last.Value.time.Ticks
                        > LOGGING_INTERVAL * recentSkeletons.Count) //LOGGING_INTERVALで指定した期間に基づきログを作成
                {
                    Skeleton copyOfclosestSkeleton = new Skeleton();
                    recentSkeletons.AddFirst(new SkeletonLog(copyOfJointsOfClosestSkeleton, DateTime.Now));
                 //   System.Diagnostics.Debug.WriteLine( recentSkeletons.First.Value.skeleton.Position.X);
                }
                //ひとまず直近２０断面のスケルトンを保持
                if (recentSkeletons.Count > LOGGING_VERSIONS)
                {
                    recentSkeletons.RemoveLast();
                };
                //履歴をジェスチャー判定処理に引き渡す
                doActionSkeleton(recentSkeletons);
            }
        }


        // スケルトンを実行
        private void doActionSkeleton(LinkedList<SkeletonLog> skeletonLogs)
        {
            // 両手を得意げに広げるジェスチャー
            // （バグにて、腕組みでも発動する）
            if (ProcessCallForApplauseGesture.callForApplauseState(skeletonLogs))
            {
                this.ppController.playApplauseSound();
            }
            // 右手を右に振るジェスチャー
                // 右手を３秒間　右に振ったままでも進む(hasNextStateメソッド)
            else if (ProcessForwardBackGesture.nextState(skeletonLogs.First.Value.joints)
                || ProcessForwardBackGesture.hasNextState(skeletonLogs))
            {
                HandHeight handHeight = ProcessForwardBackGesture.handHeightState(skeletonLogs.First.Value.joints, true);
                this.ppController.setEffect( handHeight);
                this.ppController.next();
                
            // 左手を左に振るジェスチャー
            }
            else if (ProcessForwardBackGesture.backState(skeletonLogs.First.Value.joints))
            {
                HandHeight handHeight = ProcessForwardBackGesture.handHeightState(skeletonLogs.First.Value.joints, false);
                this.ppController.setEffect(handHeight);
                this.ppController.back();
                
            }

            // 蹴りのジェスチャー
            if (ProcessKickGesture.kickState(skeletonLogs.First.Value.joints))
            {
                if (ProcessKickGesture.footHeightState() == FootHeight.High)
                {
                    // ハイキック処理
                    this.ppController.drawBigX();
                }
                else
                {
                    // その他キック処理
                    this.ppController.changeFontSize(5);
                }
            }

            // 猫ポーズを判定するジェスチャー
            if (ProcessCatGesture.pauseState(skeletonLogs.First.Value.joints))
            {
                // 猫をパワポに表示させる
                // this.ppController.next();

                // Random クラスの新しいインスタンスを生成する
                Random cRandom = new System.Random();

                this.ppController.showCat( cRandom.Next(1,4));
            }

            // スケルトン情報を画面に描画する
            ProcessSkeletonDrawGesture.changeSkeletonDrawActive(skeletonLogs.First.Value.joints);
            if (ProcessSkeletonDrawGesture.checkState())
            {
                this.ppController.drawSkeleton(ProcessSkeletonDrawGesture.getPositions(sensor, skeletonLogs.First.Value.joints));

            }
           

            // 電灯のスイッチオンオフのジェスチャー
            if (ProcesLightSwitchGesture.hasSwitched(skeletonLogs))
            {
                this.ppController.turnBlack();
            }

            // お手上げのジェスチャー
            if (ProcesSurrenderGesture.hasSurrendered(skeletonLogs))
            {
                this.ppController.playGong();
            }

            //ウニャーを判定するジェスチャー

            // 昇竜剣を判定する
        }

        /// <summary>
        /// スケルトンを描画する
        /// </summary>
        /// <param name="kinect"></param>
        /// <param name="skeletonFrame"></param>
        private void DrawSkeleton(KinectSensor kinect, SkeletonFrame skeletonFrame)
        {
            // スケルトンのデータを取得する
            Skeleton[] skeletons = new Skeleton[skeletonFrame.SkeletonArrayLength];
            skeletonFrame.CopySkeletonDataTo(skeletons);

            canvasSkeleton.Children.Clear();

            // トラッキングされているスケルトンのジョイントを描画する
            foreach (Skeleton skeleton in skeletons)
            {
                // スケルトンがトラッキング状態(デフォルトモード)の場合は、ジョイントを描画する
                if (skeleton.TrackingState == SkeletonTrackingState.Tracked)
                {
                    // ジョイントを描画する
                    foreach (Joint joint in skeleton.Joints)
                    {
                        // ジョイントがトラッキングされていなければ次へ
                        if (joint.TrackingState == JointTrackingState.NotTracked)
                        {
                            continue;
                        }

                        // ジョイントの座標を描く
                        DrawEllipse(kinect, joint.Position);
                    }
                }
                // スケルトンが位置追跡(ニアモードの)の場合は、スケルトン位置(Center hip)を描画する
                else if (skeleton.TrackingState == SkeletonTrackingState.PositionOnly)
                {
                    // スケルトンの座標を描く
                    DrawEllipse(kinect, skeleton.Position);
                }
            }
        }

        /// <summary>
        /// ジョイントの円を描く
        /// </summary>
        /// <param name="kinect"></param>
        /// <param name="position"></param>
        private void DrawEllipse(KinectSensor kinect, SkeletonPoint position)
        {
            const int R = 5;

            // スケルトンの座標を、RGBカメラの座標に変換する
            ColorImagePoint point = kinect.MapSkeletonPointToColor(position, kinect.ColorStream.Format);
            
            // 円を描く
            canvasSkeleton.Children.Add(new Ellipse()
            {
                Fill = new SolidColorBrush(Colors.Red),
                Margin = new Thickness(point.X - R, point.Y - R, 0, 0),
                Width = R * 2,
                Height = R * 2,
            });
        }

        // キー操作
        void MainWindow_KeyDown(object sender, KeyEventArgs e)
        {
            try
            {
                if (e.Key == Key.U)
                {
                    KinectSensor.KinectSensors[0].ElevationAngle += 5;
                }
                else if (e.Key == Key.D)
                {
                    KinectSensor.KinectSensors[0].ElevationAngle -= 5;
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
        }
    }



   



}
